package com.undsf.ezhttp;

import com.google.gson.Gson;
import com.google.gson.JsonObject;
import com.google.gson.reflect.TypeToken;
import okhttp3.Request;
import okhttp3.Response;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;

import java.io.*;
import java.lang.reflect.Type;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * Created by Arathi on 2017/4/6.
 */
public class CachedHttpClient extends EasyHttpClient {
    private Pattern PATTERN_CONTENT_TYPE = Pattern.compile("([\\w\\d]+/[\\w\\d]+);.*charset=([\\w\\d\\-]+)");

    protected Gson gson;
    protected String cachePath;
    protected Map<String, CacheInfo> cacheDict;
    protected boolean autoSave;

    public CachedHttpClient(String cachePath) {
        gson = new Gson();
        this.cachePath = cachePath;
        autoSave = true;
        loadCacheDict();
    }

    public String get(String url) throws IOException {
        return get(url, null);
    }

    public String get(String url, Map<String,String> paramMap) throws IOException {
        String cacheKey = GetCacheKey(url, paramMap);
        String content = readFromCache(cacheKey);
        if (content == null) {
            Request request = buildGetRequest(url, paramMap);
            Response response = newCall(request).execute();

            byte[] binaryContent = response.body().bytes();

            CacheInfo cacheInfo = null;
            do {
                String contentType = response.header("content-type");
                cacheInfo = buildCacheInfo(url, paramMap, contentType);
                if (cacheInfo.mime != null && cacheInfo.charset != null) break;

                String html = new String(binaryContent);
                Document doc = Jsoup.parse(html);
                Element metaContentType = doc.select("meta[http-equiv=Content-Type]").first();
                if (metaContentType != null) {
                    contentType = metaContentType.attr("content");
                    cacheInfo = buildCacheInfo(url, paramMap, contentType);
                }
                if (cacheInfo.mime != null && cacheInfo.charset != null) break;

                Element metaCharset = doc.select("meta[charset]").first();
                if (metaCharset != null) {
                    cacheInfo.charset = metaCharset.attr("charset");
                    if (cacheInfo.mime == null) {
                        cacheInfo.mime = "text/html";
                    }
                }
            }
            while (false);

            boolean writeSuccess = writeToCache(cacheKey, binaryContent, cacheInfo);
            if (writeSuccess) {
                cacheDict.put(cacheKey, cacheInfo);
                if (autoSave) saveCacheDict();
            }

            content = new String(binaryContent, cacheInfo.charset);
        }
        return content;
    }

    public CacheInfo buildCacheInfo(String url, Map<String,String> paramMap, String contentType) {
        CacheInfo cacheInfo = new CacheInfo();
        cacheInfo.url = url;
        cacheInfo.params = paramMap;
        cacheInfo.mime = null;
        cacheInfo.charset = null;

        Matcher matcher = PATTERN_CONTENT_TYPE.matcher(contentType);
        if (matcher.find()) {
            cacheInfo.mime = matcher.group(1);
            cacheInfo.charset = matcher.group(2);
        }

        return cacheInfo;
    }

    public void loadCacheDict() {
        File cacheDictFile = new File(cachePath + "/_cache_dict.json");
        boolean readFlag = false;
        do {
            String json = FileUtils.Read(cacheDictFile, StandardCharsets.UTF_8);
            if (json == null || json.isEmpty()) {
                break;
            }
            Type type = new TypeToken<Map<String, CacheInfo>>(){}.getType();
            cacheDict = gson.fromJson(json, type);
            if (!cacheDict.isEmpty()) {
                readFlag = true;
            }
        }
        while (false);
        if (!readFlag) {
            cacheDict = new HashMap<>();
        }
    }

    public void saveCacheDict() {
        File cacheDictFile = new File(cachePath + "/_cache_dict.json");
        String json = gson.toJson(cacheDict);
        boolean saveSuccess = FileUtils.Write(cacheDictFile, json, StandardCharsets.UTF_8);
    }

    public String readFromCache(String cacheKey) {
        if (!cacheDict.containsKey(cacheKey)) {
            // 缓存记录不存在
            return null;
        }
        CacheInfo cacheInfo = cacheDict.get(cacheKey);
        String cacheFileFullName = getCacheFileFullName(cacheKey, cacheInfo);

        File cacheFile = new File(cacheFileFullName);
        if (!cacheFile.exists()) {
            // 缓存文件不存在，缓存失效
            cacheDict.remove(cacheKey);
            return null;
        }

        Charset charset = (cacheInfo.charset != null) ? Charset.forName(cacheInfo.charset) : StandardCharsets.UTF_8;
        String content = FileUtils.Read(cacheFile, charset);

        if (content == null || content.isEmpty()) {
            cacheDict.remove(cacheKey);
            return null;
        }

        return content;
    }

    @Deprecated
    public boolean writeToCache(String cacheKey, String content, CacheInfo cacheInfo) {
        Charset charset = (cacheInfo.charset != null) ? Charset.forName(cacheInfo.charset) : StandardCharsets.UTF_8;
        return writeToCache(cacheKey, content.getBytes(charset), cacheInfo);
    }

    public boolean writeToCache(String cacheKey, byte[] content, CacheInfo cacheInfo) {
        String cacheFileFullName = getCacheFileFullName(cacheKey, cacheInfo);
        File cacheFile = new File(cacheFileFullName);
        return FileUtils.Write(cacheFile, content);
    }

    public String getCacheFileFullName(String cacheKey, CacheInfo cacheInfo) {
        // 决定缓存扩展名
        String extension = "";
        if (cacheInfo.mime.contains("application/json")) {
            extension = ".json";
        }
        else if (cacheInfo.mime.contains("text/html")) {
            extension = ".html";
        }
        // 拼接文件绝对路径
        String cacheFileFullName = cachePath + "/" + cacheKey + extension;
        return cacheFileFullName;
    }

    public static String GetCacheKey(String url, Map<String,String> paramMap)
    {
        String hash1 = HashUtils.Hash(url, "MD5");
        String hash2 = HashUtils.Hash(url, "SHA-1");
        return hash1;  // hash1 + "_" + hash2;
    }
}
